Python 黑白棋(翻转棋)小游戏

[Python] 黑白棋(翻转棋)小游戏

游戏介绍

黑白棋(Reversi or Othello)在西方和日本很流行。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。

规则

黑白棋的每颗棋子由黑白两色组成,一面白,一面黑。每次落子,把本方颜色的棋子放在棋盘的空格上,若在横、竖、斜八个方向的任一方向上有本方棋子,则被夹在中间的对手棋子全部翻转为本方棋子颜色;并且仅在可以翻转棋 子的地方才能落子。如果一方至少有一步合法棋步可下,他就必须落子,不得弃权。

棋盘已满或双方都没有棋子可下时棋局结束,以棋子数目来计算胜负,棋子多的一方获胜。在棋盘还没有下满时,如果一方的棋子已经被对方吃光,则棋局也结束,将对手棋子吃光的一方获胜。

胜负判定

两位玩家轮流下棋,直到一方没有符合规则的落子位置,在这种情况下,剩下的一方 继续下棋,直到对手有了可以落子的位置。此时,恢复两者轮流下棋的顺序。如果一方落子在非法位置,则视为放弃本次对弈,对方获胜。

游戏结束的条件:

  1. 整个棋盘满了
  2. 一方的棋子已经被对方吃光
  3. 两名玩家都没有可以落子的棋盘格
  4. 一方落子在非法位置

前3种情况以棋子数目来计算胜负,棋子多的一方获胜;第四种情况判定对方获胜。

人机对弈流程

首先,程序询问用户棋盘的大小。接着,程序询问用户“计算机持黑棋还是白棋”。

在本程序中,我们用字母’X’代表黑棋, 用字母’O’代表白棋,并且假设总是黑棋玩家先走。

所以,如果计算机持黑棋,计算机就先走; 否则,程序提示人类玩家先走。

每走一步,程序输出棋盘。黑白棋玩家轮流下棋,直到一个玩家无符合规则的落子位置。

此时,程序输出信息“O player has no valid move.”(假设白棋玩家无棋可走),并且提示黑棋玩家继续下棋。

每走一步,程序除输出棋盘外,还要检测游戏是否结束。如果程序检查出游戏结束,输出输赢信息并中止程序。输赢信息可以是: “O player wins.”, “X player wins.” 或者“Draw!”. 如果用户落子非法,程序应检测到并且输出“Invalid move.”, 结束程序,宣布赢家。

计算机选择落子位置的策略

对每个可能的落子位置,都进行尝试,计算该位置的“分值”(可以翻转的对手棋子数量),分值越高则在该位置落子越有利。计算每个可能位置的分值,选择最大值位置落子。需要注意的是:可能有2个或多个棋盘格有相同的分值。这种情况下,选择行字母最小的棋盘格。如果两个棋盘格分值相同且在同一行,则选择列字母较小的棋盘格。

程序执行

代码

完整代码见
代码仓库

棋盘代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python 
# -*- coding: utf-8 -*-

class Board():
def __init__(self, n):
self.n = n
self.board = self.generateBoard()
self.chess = {0: '.', 1: 'O', 2: 'X'}

def generateBoard(self):
# 0 empty 1 white 2 black
i = int(self.n / 2)
board = [[0] * self.n for _ in range(self.n)]
board[i][i]=board[i-1][i-1] = 1
board[i][i-1]=board[i-1][i] = 2
return board

def draw(self):
index = 'abcdefghijklmnopqrstuvwxyz'
print(' ',*index[:self.n])
for h,row in zip(index,self.board):
print(h,*map('.OX'.__getitem__,row))
print()

游戏逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/python 
# -*- coding: utf-8 -*-

from board import Board
import itertools
import operator
import collections
from functools import reduce
from constant import Status

class Reversi():

_DIRECTIONS = [(1,0),(1,1),(1,-1),(-1,0),(-1,1),(-1,-1),(0,1),(0,-1)]

def __init__(self, n, turn):
self.n = n # board dimension
self.b = Board(n) # board
self.turn = 0 if turn == 'X' or turn == 'x' else 1 # player turn
self.step = 1 # game step
self.status = Status.WAIT # game status

def isValidPosition(self,x,y):
return 0 <= x < self.n and 0 <= y < self.n

def nextPosition(self,direction,x,y):
x+=direction[0]
y+=direction[1]
return x,y

def score(self,r,c):
return list(itertools.chain.from_iterable([self.scoreDirection(r+m[0],c+m[1],m,self.step%2+1,[]) for m in Reversi._DIRECTIONS]))

def scoreDirection(self,x,y,direction,color,turn):
if not self.isValidPosition(x,y) or self.b.board[x][y]==0 :
return []
if self.b.board[x][y]!=color:
turn+=[(x,y)]
return self.scoreDirection(*self.nextPosition(direction,x,y),direction,color,turn)
else:
return turn

def checkPut(self, pos):
# check person put
assert len(pos)>=2 , 'move position disable'
r = ord(pos[0]) - 97
c = ord(pos[1]) - 97
assert 0 <= r < self.n and 0 <= c < self.n, 'move position disable'
turnList = self.score(r, c)
if turnList:
# turn chess
for x,y in turnList+[(r,c)]:
self.b.board[x][y] = self.step % 2+1
return True
else:
return False

def checkGame(self):
# check game status
empty,oNum,xNum = operator.itemgetter(0,1,2)(collections.Counter(itertools.chain.from_iterable(self.b.board)))
hasPut = True
pos,turnList = self.aiPut()
if not turnList:
self.step += 1
posNext,turnListNext = self.aiPut()
if not turnListNext:
hasPut = False
else:
self.step -= 1
print('{} player has no valid move'.format(self.b.chess[self.step % 2+1]))
self.step -= 1
self.turn -= 1
print('{} player go on'.format(self.b.chess[self.step % 2+1]))
if empty ==0 or oNum==0 or xNum == 0 or not hasPut:
self.status = [Status.DRAW.value,Status.OWIN.value,Status.XWIN.value][(oNum > xNum)-(oNum<xNum)]

def cmp(self,a,b):
if len(a[1])>len(b[1]):
return a
elif len(a[1])==len(b[1]) and a[0]<b[0]:
return a
else:
return b

def aiPut(self):
# computer put
allPos = filter(lambda pos : self.b.board[pos[0]][pos[1]]==0,itertools.product(range(self.n),repeat=2))
allScoreForPos = map(lambda pos: [pos,self.score(pos[0],pos[1])],allPos)
maxScorePos = reduce(self.cmp,allScoreForPos,[(),[]])
return maxScorePos[0],maxScorePos[1]

def aiPlay(self):
pos,turnList = self.aiPut()
if turnList:
print('Computer places {} at {}'.format(self.b.chess[self.step % 2+1],chr(pos[0]+97)+chr(pos[1]+97)))
for x,y in turnList+[pos]:
self.b.board[x][y] = self.step % 2+1
reversi.b.draw()
self.step += 1
self.turn += 1

def pPlay(self):
pos = input('Enter move for {} (RowCol):'.format(self.b.chess[self.step % 2+1]))
if self.checkPut(pos):
reversi.b.draw()
self.step += 1
self.turn += 1
else:
print('Invalid move')

def play(self):
self.status = Status.ONGOING
plays = [self.aiPlay,self.pPlay]
while self.status == Status.ONGOING:
plays[self.turn % len(plays)]()
self.checkGame()
else:
print('Game over. {}'.format(Status(self.status)))

if __name__ == "__main__":
print('Enter the board dimension:')
try:
n = int(input())
except Exception as e:
print('the board dimension is invalid, start game with default dimension = 4')
n = 4
assert 4 <= n <= 26 and n % 2 == 0, 'the board dimension is disable'
print('Computer plays (X/O):')
turn = input()
assert turn in ['X','x','O', 'o'], 'the symbol of computer is disable'
# generate game
reversi = Reversi(n, turn)
# draw board
reversi.b.draw()
reversi.play()
input('Enter to quit')